package il.ac.bgu.cs.heuristics.graph;

import il.ac.bgu.cs.heuristics.graph.pddl.PddlUtils;
import il.ac.bgu.cs.heuristics.graph.pddl.SafeCast;
import il.ac.bgu.cs.heuristics.graph.pddl.auxiliary.ContradictionResolver;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.ExpressionParser;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.AbstractParameter;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.Action;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.ITypedConstant;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.LogicalExpression;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.Predicate;
import il.ac.bgu.cs.heuristics.graph.types.IActionGraph;
import il.ac.bgu.cs.heuristics.graph.types.IPath;
import il.ac.bgu.cs.heuristics.graph.types.IState;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.jgrapht.alg.BellmanFordShortestPath;
import org.jgrapht.graph.SimpleDirectedWeightedGraph;

import pddl4j.Problem;

/**
 * Implementation of the Action Graph concept
 * 
 * @author Ronen Pinko
 */
public final class ActionGraph implements IActionGraph {

	private static final int										ACTION_EDGE_WEIGHT				= 1;
	private static final int										NON_CONTRADICTING_EDGE_WEIGHT	= 0;

	/** the underlying implementation of the graph */
	private final SimpleDirectedWeightedGraph<IState, ActionEdge>	graph;

	/** the precondition states */
	private final Set<IState>										preconditionStateSet			= CollectionUtils
																											.newLinkedHashSet();

	/** the effect states */
	private final Set<IState>										effectStateSet					= CollectionUtils
																											.newLinkedHashSet();

	/** the init state. may be changed with {@link #setInitState(IState)} */
	private IState													initState;

	/** the goal state */
	private final IState											goalState;

	private final GraphBuildEssentials								essentials;

	private final ContradictionResolver								resolver;

	public ActionGraph(GraphBuildEssentials essentials) {
		this.graph = new SimpleDirectedWeightedGraph<IState, ActionEdge>(
				new ActionEdgeFactory(preconditionStateSet, effectStateSet));
		this.essentials = essentials;
		this.goalState = createGoalState(essentials.getProblem());
		this.resolver = essentials.getResolver();
		createEdges();
	}

	public IState getGoalState() {
		return goalState;
	}

	private void createEdges() {
		createPreconditionToEffectEdges();
		createNonContradictingEdges();
	}

	private void createNonContradictingEdges() {
		for (IState effect : effectStateSet) {
			for (IState precondition : preconditionStateSet) {
				if (!effect.isContradicting(precondition)) {
					addEdgeToGraph(effect, precondition,
							NON_CONTRADICTING_EDGE_WEIGHT);
				}
			}
		}
	}

	private Iterable<List<ITypedConstant>> getPermutations(int i) {
		return essentials.getConstantsPermutations(i);
	}

	private void createPreconditionToEffectEdges() {
		for (Action action : essentials.getActions()) {
			final List<AbstractParameter> actionParameters = action
					.getParameters();
			final Iterable<List<ITypedConstant>> constantPermuatations = getPermutations(actionParameters
					.size());
			for (List<ITypedConstant> permutation : constantPermuatations) {
				if (PddlUtils.areTypesSame(permutation, actionParameters)) {
					final IState preconditionState = PddlUtils.createState(
							resolver, action.getPrecondition(),
							actionParameters, permutation);
					final IState effectState = PddlUtils.createState(resolver,
							action.getEffect(), actionParameters, permutation);
					preconditionStateSet.add(preconditionState);
					effectStateSet.add(effectState);
					addEdgeToGraph(preconditionState, effectState,
							ACTION_EDGE_WEIGHT);
				}
			}
		}
	}

	private void addEdgeToGraph(IState from, IState to, int weight) {
		graph.addVertex(from);
		graph.addVertex(to);
		final ActionEdge edge = graph.addEdge(from, to);
		if (edge == null) {
			int i = 0;
			i++;
		}
		graph.setEdgeWeight(edge, weight);

	}

	private State createGoalState(Problem problem) {
		final State.Builder stateBuilder = new State.Builder();
		final LogicalExpression goalExp = ExpressionParser
				.parseLogicalExpression(problem.getGoal().toTypedString());
		for (Predicate predicate : goalExp.getPredicates()) {
			final Substitution.Builder substitutionBuilder = new Substitution.Builder(
					predicate, essentials.getResolver());
			for (AbstractParameter boundVar : predicate.getParameters()) {
				substitutionBuilder.addConstant(SafeCast
						.toTypedConstant(boundVar));
			}
			stateBuilder.addSubstitution(substitutionBuilder.build());
		}
		return stateBuilder.build();
	}

	@Override
	public Set<IState> getEffects() {
		return Collections.unmodifiableSet(effectStateSet);
	}

	@Override
	public Set<IState> getNonContradictingEffects(IState goal) {
		final Set<IState> nonContradictingEffects = CollectionUtils
				.newLinkedHashSet();
		for (IState effect : effectStateSet) {
			if (!effect.isContradicting(goal)
					&& !Collections.disjoint(effect.getSubstitutions(), goal
							.getSubstitutions())) {
				// TODO check if should use disjoint on substitutions (RP,OH)
				nonContradictingEffects.add(effect);
			}
		}
		return nonContradictingEffects;
	}

	@Override
	public Set<IState> getPreconditions() {
		return Collections.unmodifiableSet(preconditionStateSet);
	}

	@Override
	public IPath getShortestPath(IState origin, IState dest) {
		final List<ActionEdge> shortestPath = BellmanFordShortestPath
				.findPathBetween(graph, origin, dest);
		return new Path(shortestPath);
	}

	@Override
	public boolean setInitState(IState newInit) {
		graph.removeVertex(initState);
		this.initState = newInit;
		boolean wasAtLeastOneConnected = false;
		for (IState precondition : preconditionStateSet) {
			if (!newInit.isContradicting(precondition)) {
				addEdgeToGraph(newInit, precondition,
						NON_CONTRADICTING_EDGE_WEIGHT);
				wasAtLeastOneConnected = true;
			}
		}
		return wasAtLeastOneConnected;
	}

}
